package services

import (
	"context"
	"errors"
	"fmt"
	"github.com/cloudwego/hertz/pkg/common/hlog"
	jsoniter "github.com/json-iterator/go"
	"gorm.io/gen"
	"gorm.io/gen/field"
	"mall_frontend_api/biz/dal"
	"mall_frontend_api/biz/handler/base"
	"mall_frontend_api/biz/model"
	"mall_frontend_api/biz/model/common"
	"mall_frontend_api/biz/model/order"
	"mall_frontend_api/biz/pack"
	"mall_frontend_api/biz/util"
	"strconv"
	"strings"
	"time"
)

type OrderService struct {
	ctx    context.Context
	common *base.Response
	dao    *dal.InitDAO
}

func NewOrderService(ctx context.Context, common *base.Response) *OrderService {
	return &OrderService{ctx: ctx, common: common, dao: dal.NewInitDAO()}
}

func (o *OrderService) CreateOrder(req *order.CreateOrderRequest, total int64) (*order.Order, error) {
	var orderNumber string
	var orderID int64
	if err := dal.Q.Transaction(func(tx *dal.Query) error {
		userAddressInfo, err := o.dao.UserAddressDAO.GetInfoByID(o.ctx, req.UserAddressID)
		if err != nil {
			hlog.CtxErrorf(o.ctx, "GetUserAddressInfo error: %v", err)
			return err
		}

		var addressIDs = []int64{
			userAddressInfo.ReceiverProvinceID,
			userAddressInfo.ReceiverCityID,
			userAddressInfo.ReceiverRegionID,
		}

		regionMap, err := o.dao.RegionDAO.MGet(o.ctx, addressIDs)
		if err != nil {
			hlog.CtxErrorf(o.ctx, "dao.MGet error: %v", err)
			return err
		}
		if req.Type == common.CreateOrderType_Product {
			orderID, orderNumber, err = o.createOrderByProduct(req, tx, userAddressInfo, regionMap)
		} else {
			orderID, orderNumber, err = o.createOrderByCart(req, tx, userAddressInfo, regionMap)
		}
		if err != nil {
			hlog.CtxErrorf(o.ctx, "createOrder error: %v", err)
			return err
		}

		if req.PaymentType == common.PaymentType_Points {
			var fields = []field.AssignExpr{
				dal.User.Points.Sub(total),
			}
			if err = o.dao.UserDAO.UpdateWithTx(o.ctx, tx, []gen.Condition{dal.User.ID.Eq(o.common.GetUserInfo().ID)}, fields); err != nil {
				hlog.CtxErrorf(o.ctx, "dao.UserDAO.UpdateWithTx error: %v", err)
				return err
			}

			// 直接支付
			payOrderField := []field.AssignExpr{
				dal.Order.Status.Value(common.OrderStatusPtr(common.OrderStatus_Paid)),
				dal.Order.PayTime.Value(time.Now()),
				dal.Order.PayStatus.Value(common.PayStatusPtr(common.PayStatus_Paid)),
			}
			if err = o.dao.OrderDAO.UpdateWithTx(o.ctx, tx, []gen.Condition{dal.Order.ID.Eq(orderID)}, payOrderField); err != nil {
				hlog.CtxErrorf(o.ctx, "dao.OrderDAO.UpdateWithTx error: %v", err)
				return err
			}

			userPointLogData := model.UserPointsLog{
				BizID:       o.common.GetBizID(),
				OrderNumber: orderNumber,
				UserID:      o.common.GetUserInfo().ID,
				Type:        common.UserPointsType_Min,
				Points:      total,
				Remark:      fmt.Sprintf("订单号 %s 购买商品消费积分 %d", orderNumber, total),
				AdminID:     0,
				CreatedBy:   o.common.GetUserNickname(),
			}
			if _, err = o.dao.UserPointsLogDAO.CreateWithTx(o.ctx, tx, &userPointLogData); err != nil {
				hlog.CtxErrorf(o.ctx, "dao.UserPointsLogDAO.CreateWithTx error: %v", err)
				return err
			}

		}
		return nil
	}); err != nil {
		hlog.CtxErrorf(o.ctx, "Transaction err %v", err)
		return nil, err
	}

	return &order.Order{OrderNumber: orderNumber, ID: orderID}, nil
}

func (o *OrderService) createOrderByProduct(req *order.CreateOrderRequest, tx *dal.Query, userAddressInfo *model.UserAddress, regionMap map[int64]*model.Region) (int64, string, error) {
	productInfo, err := o.dao.ProductDAO.GetInfoByID(o.ctx, req.ProductID)
	if err != nil {
		hlog.CtxErrorf(o.ctx, "GetInfoByID err %v", err)
		return 0, "", err
	}
	productItemInfo, err := o.dao.ProductItemDAO.GetByConditions(o.ctx, []gen.Condition{
		dal.ProductItem.ProductID.Eq(req.ProductID),
		dal.ProductItem.SkuID.Eq(req.SKUID),
	})
	if err != nil {
		hlog.CtxErrorf(o.ctx, "ProductItemDAO.GetByConditions err %v", err)
		return 0, "", err
	}

	var orderInfo = &model.Order{}
	orderInfo.OrderNumbers = util.GenOrderNumber()
	orderInfo.UserID = o.common.GetUserInfo().ID
	orderInfo.BizID = o.common.GetBizID()
	orderInfo.UserAddressID = req.UserAddressID
	orderInfo.Status = common.OrderStatus_PendingPayment
	orderInfo.ReceiverMobile = userAddressInfo.ReceiverMobile
	orderInfo.ReceiverName = userAddressInfo.ReceiverName
	orderInfo.ReceiverEmail = userAddressInfo.ReceiverEmail
	orderInfo.ReceiverAddress = userAddressInfo.ReceiverAddress
	orderInfo.PayType = req.PaymentType
	if p, ok := regionMap[userAddressInfo.ReceiverRegionID]; ok {
		orderInfo.ReceiverRegion = p.Name
	}
	if p, ok := regionMap[userAddressInfo.ReceiverProvinceID]; ok {
		orderInfo.ReceiverProvince = p.Name
	}
	if p, ok := regionMap[userAddressInfo.ReceiverCityID]; ok {
		orderInfo.ReceiverCity = p.Name
	}
	orderInfo.UserAddressID = req.UserAddressID
	orderInfo.SubmitTime = util.GetNowTime()

	var orderItemInfo = &model.OrdersItem{}
	orderItemInfo.Status = common.OrderStatus_PendingPayment
	orderItemInfo.Price = productItemInfo.SellPrice
	orderItemInfo.ProductID = productItemInfo.ProductID
	orderItemInfo.ProductNumber = req.GetProductNumber()
	orderItemInfo.BizID = o.common.GetBizID()
	orderItemInfo.ProductName = productInfo.Name
	orderItemInfo.ProductIcon = productInfo.Image
	var specIDs []int64
	var specValueIDs []int64
	specList := strings.Split(productItemInfo.SkuValue, ":")
	for _, i := range specList {
		l := strings.Split(i, "-")
		id, err := strconv.ParseInt(l[1], 10, 64)
		if err != nil {
			hlog.CtxErrorf(o.ctx, "strconv.ParseInt err %v", err)
			return 0, "", err
		}
		if l[0] == "s" {
			specIDs = append(specIDs, id)
		} else {
			specValueIDs = append(specValueIDs, id)
		}
	}
	specMap, err := o.dao.ProductSpecDAO.MGet(o.ctx, specIDs)
	if err != nil {
		hlog.CtxErrorf(o.ctx, "dao.ProductSpecDAO.MGet err %v", err)
		return 0, "", err
	}
	specValueMap, err := o.dao.ProductSpecValueDAO.MGet(o.ctx, specValueIDs)
	if err != nil {
		hlog.CtxErrorf(o.ctx, "dao.ProductSpecValueDAO.Mget err %v", err)
		return 0, "", err
	}

	var orderProductItem []*model.OrderItemProductItem
	for key, specID := range specIDs {
		spec, ok := specMap[specID]
		specValue, sok := specValueMap[specValueIDs[key]]
		if !ok || !sok {
			hlog.CtxErrorf(o.ctx, "spec product item not found")
			return 0, "", errors.New("spec product item not found")
		}
		orderProductItem = append(orderProductItem, &model.OrderItemProductItem{
			Key:   spec.Name,
			Value: specValue.Name,
		})
	}
	orderProductItemStr, err := jsoniter.MarshalToString(&orderProductItem)
	if err != nil {
		hlog.CtxErrorf(o.ctx, "jsoniter.MarshalToString err %v", err)
		return 0, "", err
	}
	orderItemInfo.ProductAttr = orderProductItemStr

	orderInfo.TotalPrice = orderItemInfo.Price * float64(req.ProductNumber)
	orderInfo.CreatedBy = o.common.GetUserNickname()
	orderInfo, err = o.dao.OrderDAO.CreateWithTx(o.ctx, tx, orderInfo)
	if err != nil {
		hlog.CtxErrorf(o.ctx, "dao.OrderDAO.CreateWithTx err %v", err)
		return 0, "", err
	}
	orderItemInfo.OrderID = orderInfo.ID
	orderItemInfo.CreatedBy = o.common.GetUserNickname()
	if _, err = o.dao.OrdersItemDAO.CreateWithTx(o.ctx, tx, orderItemInfo); err != nil {
		hlog.CtxErrorf(o.ctx, "dao.OrdersItemDAO.CreateWithTx err %v", err)
		return 0, "", err
	}
	return orderInfo.ID, orderInfo.OrderNumbers, nil
}

func (o *OrderService) createOrderByCart(req *order.CreateOrderRequest, tx *dal.Query, userAddressInfo *model.UserAddress, regionMap map[int64]*model.Region) (int64, string, error) {
	cartMap, err := o.dao.CartDAO.MGet(o.ctx, req.CartIDs)
	if err != nil {
		hlog.CtxErrorf(o.ctx, "dao.MGet error: %v", err)
		return 0, "", err
	}

	var orderInfo = &model.Order{}
	orderInfo.OrderNumbers = util.GenOrderNumber()
	orderInfo.UserID = o.common.GetUserInfo().ID
	orderInfo.BizID = o.common.GetBizID()
	orderInfo.UserAddressID = req.UserAddressID
	orderInfo.Status = common.OrderStatus_PendingPayment
	orderInfo.ReceiverMobile = userAddressInfo.ReceiverMobile
	orderInfo.ReceiverName = userAddressInfo.ReceiverName
	orderInfo.ReceiverEmail = userAddressInfo.ReceiverEmail
	orderInfo.ReceiverAddress = userAddressInfo.ReceiverAddress
	orderInfo.PayType = req.PaymentType
	if p, ok := regionMap[userAddressInfo.ReceiverRegionID]; ok {
		orderInfo.ReceiverRegion = p.Name
	}
	if p, ok := regionMap[userAddressInfo.ReceiverProvinceID]; ok {
		orderInfo.ReceiverProvince = p.Name
	}
	if p, ok := regionMap[userAddressInfo.ReceiverCityID]; ok {
		orderInfo.ReceiverCity = p.Name
	}
	orderInfo.UserAddressID = req.UserAddressID
	orderInfo.SubmitTime = util.GetNowTime()

	var productIDs []int64
	for _, cart := range cartMap {
		productIDs = append(productIDs, cart.ProductID)
	}
	productMap, err := o.dao.ProductDAO.MGet(o.ctx, productIDs)
	if err != nil {
		hlog.CtxErrorf(o.ctx, "dao.ProductDAO.MGet err %v", err)
		return 0, "", err
	}
	productItemAllMap, err := o.dao.ProductItemDAO.MGetByProductIDs(o.ctx, productIDs)
	if err != nil {
		hlog.CtxErrorf(o.ctx, "dao.ProductItemDAO.MGet err %v", err)
		return 0, "", err
	}

	var orderItemList []*model.OrdersItem
	var totalPrice float64
	for _, cart := range cartMap {
		productInfo, pok := productMap[cart.ProductID]
		if !pok {
			hlog.CtxErrorf(o.ctx, "product not found")
			return 0, "", errors.New("product not found")
		}
		var productItemInfo *model.ProductItem
		productItemList, piok := productItemAllMap[cart.ProductID]
		if !piok {
			hlog.CtxErrorf(o.ctx, "product item not found")
			return 0, "", errors.New("product item not found")
		}

		for _, item := range productItemList {
			if cart.SkuID == item.SkuID {
				productItemInfo = item
			}
		}
		if productItemInfo == nil {
			hlog.CtxErrorf(o.ctx, "product item not found")
			return 0, "", errors.New("product item not found")
		}

		var orderItemInfo = &model.OrdersItem{}
		orderItemInfo.OrderID = orderInfo.ID
		orderItemInfo.Status = common.OrderStatus_PendingPayment
		orderItemInfo.Price = productItemInfo.SellPrice
		orderItemInfo.ProductID = productItemInfo.ProductID
		orderItemInfo.ProductNumber = cart.Number
		orderItemInfo.BizID = o.common.GetBizID()
		orderItemInfo.ProductName = productInfo.Name
		orderItemInfo.ProductIcon = productInfo.Image
		totalPrice = totalPrice + orderItemInfo.Price*float64(cart.Number)
		var specIDs []int64
		var specValueIDs []int64
		specList := strings.Split(productItemInfo.SkuValue, ":")
		for _, i := range specList {
			l := strings.Split(i, "-")
			id, err := strconv.ParseInt(l[1], 10, 64)
			if err != nil {
				hlog.CtxErrorf(o.ctx, "strconv.ParseInt err %v", err)
				return 0, "", err
			}
			if l[0] == "s" {
				specIDs = append(specIDs, id)
			} else {
				specValueIDs = append(specValueIDs, id)
			}
		}
		specMap, err := o.dao.ProductSpecDAO.MGet(o.ctx, specIDs)
		if err != nil {
			hlog.CtxErrorf(o.ctx, "dao.ProductSpecDAO.MGet err %v", err)
			return 0, "", err
		}
		specValueMap, err := o.dao.ProductSpecValueDAO.MGet(o.ctx, specValueIDs)
		if err != nil {
			hlog.CtxErrorf(o.ctx, "dao.ProductSpecValueDAO.Mget err %v", err)
			return 0, "", err
		}
		var orderProductItem []*model.OrderItemProductItem
		for key, specID := range specIDs {
			spec, ok := specMap[specID]
			specValue, sok := specValueMap[specValueIDs[key]]
			if !ok || !sok {
				hlog.CtxErrorf(o.ctx, "spec product item not found")
				return 0, "", errors.New("spec product item not found")
			}
			orderProductItem = append(orderProductItem, &model.OrderItemProductItem{
				Key:   spec.Name,
				Value: specValue.Name,
			})
		}
		orderProductItemStr, err := jsoniter.MarshalToString(&orderProductItem)
		if err != nil {
			hlog.CtxErrorf(o.ctx, "jsoniter.MarshalToString err %v", err)
			return 0, "", err
		}
		orderItemInfo.ProductAttr = orderProductItemStr
		orderItemInfo.CreatedBy = o.common.GetUserNickname()
		orderItemList = append(orderItemList, orderItemInfo)
	}

	orderInfo.TotalPrice = totalPrice
	orderInfo.CreatedBy = o.common.GetUserNickname()
	orderInfo, err = o.dao.OrderDAO.CreateWithTx(o.ctx, tx, orderInfo)
	if err != nil {
		hlog.CtxErrorf(o.ctx, "dao.OrderDAO.CreateWithTx err %v", err)
		return 0, "", err
	}

	for key, _ := range orderItemList {
		orderItemList[key].OrderID = orderInfo.ID
	}

	if _, err = o.dao.OrdersItemDAO.BatchCreateWithTx(o.ctx, tx, orderItemList); err != nil {
		hlog.CtxErrorf(o.ctx, "dao.OrdersItemDAO.BatchCreateWithTx err %v", err)
		return 0, "", nil
	}

	if err = o.dao.CartDAO.DeleteWithTx(o.ctx, tx, []gen.Condition{dal.Cart.ID.In(req.CartIDs...)}); err != nil {
		hlog.CtxErrorf(o.ctx, "dao.ProductDAO.DeleteWithTx err %v", err)
		return 0, "", err
	}

	return orderInfo.ID, orderInfo.OrderNumbers, nil
}

func (o *OrderService) GetOrderList(req *order.GetOrderListRequest) ([]*order.Order, int64, error) {
	var conditions = []gen.Condition{dal.Order.UserID.Eq(o.common.GetUserInfo().ID)}
	if req.Status != nil {
		conditions = append(conditions, dal.Order.Status.Eq(req.Status))
	}
	orderList, total, err := o.dao.OrderDAO.GetListWithTotal(o.ctx, &dal.OrderListParams{
		Condition: conditions,
		Page:      o.common.GetPage(),
		Size:      o.common.GetSize(),
		Sort:      []field.Expr{dal.Order.CreatedAt.Desc()},
	})
	if err != nil {
		hlog.CtxErrorf(o.ctx, "dao.OrderDAO.GetListWithTotal err %v", err)
		return nil, 0, err
	}
	var orderIDs []int64
	for _, orderInfo := range orderList {
		orderIDs = append(orderIDs, orderInfo.ID)
	}
	orderItemList, err := o.dao.OrdersItemDAO.GetList(o.ctx, []gen.Condition{dal.OrdersItem.OrderID.In(orderIDs...)}, 0, 0)
	if err != nil {
		hlog.CtxErrorf(o.ctx, "dao.OrdersItemDAO.GetList err %v", err)
		return nil, 0, err
	}
	var orderItemMap = make(map[int64][]*model.OrdersItem)
	for _, item := range orderItemList {
		orderItemMap[item.OrderID] = append(orderItemMap[item.OrderID], item)
	}
	return pack.OrderList(orderList, orderItemMap), total, nil
}

func (o *OrderService) GetOrderInfo(req *order.GetOrderInfoRequest) (*order.Order, error) {
	orderList, err := o.dao.OrderDAO.GetInfoByID(o.ctx, req.ID)
	if err != nil {
		hlog.CtxErrorf(o.ctx, "dao.OrderDAO.GetInfoByID err %v", err)
		return nil, err
	}
	orderItemList, err := o.dao.OrdersItemDAO.GetList(o.ctx, []gen.Condition{dal.OrdersItem.OrderID.Eq(req.ID)}, 0, 0)
	if err != nil {
		hlog.CtxErrorf(o.ctx, "dao.OrdersItemDAO.GetList err %v", err)
		return nil, err
	}

	return pack.OrderInfo(orderList, orderItemList), nil
}

func (o *OrderService) CancelOrder(req *order.CancelOrderRequest) error {
	if err := o.dao.OrderDAO.Update(o.ctx, []gen.Condition{dal.Order.ID.Eq(req.ID)}, []field.AssignExpr{
		dal.Order.Status.Value(common.OrderStatusPtr(common.OrderStatus_Cancel)),
		dal.Order.CancelTime.Value(time.Now()),
	}); err != nil {
		hlog.CtxErrorf(o.ctx, "dao.OrderDAO.Update err %v", err)
		return err
	}
	return nil
}

func (o *OrderService) PaymentOrder(req *order.PaymentOrderRequest) error {
	if err := o.dao.OrderDAO.Update(o.ctx, []gen.Condition{dal.Order.ID.Eq(req.ID)}, []field.AssignExpr{
		dal.Order.Status.Value(common.OrderStatusPtr(common.OrderStatus_Paid)),
		dal.Order.PayTime.Value(time.Now()),
		dal.Order.PayStatus.Value(common.PayStatusPtr(common.PayStatus_Paid)),
	}); err != nil {
		hlog.CtxErrorf(o.ctx, "dao.OrderDAO.Update err %v", err)
		return err
	}
	return nil
}

func (o *OrderService) ConfirmReceiptOrder(req *order.ConfirmReceiptOrderRequest) error {
	if err := o.dao.Q.Transaction(func(tx *dal.Query) error {
		if err := o.dao.OrderDAO.UpdateWithTx(o.ctx, tx, []gen.Condition{dal.Order.ID.Eq(req.ID)}, []field.AssignExpr{
			dal.Order.Status.Value(common.OrderStatusPtr(common.OrderStatus_ConfireReceipt)),
			dal.Order.PayTime.Value(time.Now()),
		}); err != nil {
			hlog.CtxErrorf(o.ctx, "dao.OrderDAO.Update err %v", err)
			return err
		}
		return nil
	}); err != nil {
		hlog.CtxErrorf(o.ctx, "dao.QTransaction err %v", err)
		return err
	}

	return nil
}

func (o *OrderService) GetPaymentSelect(req *order.GetPaymentSelectRequest) ([]*order.PaymentSelect, error) {
	return []*order.PaymentSelect{
		{
			ID:   common.PaymentType_Points,
			Name: "Points",
		},
		{
			ID:   common.PaymentType_Alipay,
			Name: "Alipay",
		},
	}, nil
}
